/*
 * Systemprogrammierung
 * Multiplayer-Quiz
 *
 * Server
 *
 * user.c: Implementierung der User-Verwaltung
 */

#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <pthread.h>
#include "user.h"
#include "common/rfc.h"

struct UserInfo *oUserList = NULL;

pthread_mutex_t oUserMutex;

// Initializes the user mutex
void user_InitMutex() {
	int iTemp;
	iTemp = pthread_mutex_init(&oUserMutex, NULL);
	if (iTemp != 0) {
		debugPrint("\nUnable to init user mutex!");
	}
}

// Locks the user mutex
void user_SetMutex() {
	//  Abbruch des Threads verhindern
	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
	// Mutex sperren
	pthread_mutex_lock(&oUserMutex);
}

// Frees the user mutex
void user_FreeMutex() {
	//Mutex entsperren
	pthread_mutex_unlock(&oUserMutex);
	// Abbruch des Threads ermöglichen
	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
}

// Returns the number of logged in users
int user_GetUserCount() {
	int i = 0;
	struct UserInfo *oTemp = oUserList;

	while (oTemp != NULL) {
		i++;
		oTemp = oTemp -> ptrNext;
	}
	return i;
}

// Returns 0 for not taken and 1 for already taken
int user_UsernameAlreadyTaken(char *sUsername) {
	struct UserInfo *oTemp = oUserList;

	while (1) {
		if (oTemp == NULL) {
			return 0;
		}
		else {
			if (strcmp(oTemp -> sUsername, sUsername) == 0) { return 1; }
			oTemp = oTemp -> ptrNext;
		}
	}

	return 0;
}

// Returns 1 if the given iUserID is already taken, 0 otherwise
int user_UserIDAlreadyInUse(uint8_t iUserID) {
	struct UserInfo oTempUserInfo;

	if (oUserList == NULL) { return 0; }

	oTempUserInfo = *oUserList;
	while (1) {
		if (oTempUserInfo.iUserID == iUserID) { return 1; }
		if (oTempUserInfo.ptrNext == NULL) { return 0; }
		oTempUserInfo = *oTempUserInfo.ptrNext;
	}

	return 0;
}

// Returns a free User-ID, returns -1 if no free ID is available
int8_t user_GetFreeUserID() {
	int8_t iNewUserID;

	for (iNewUserID = 0; iNewUserID <= 255; iNewUserID++) {
		if (user_UserIDAlreadyInUse(iNewUserID) == 0) { return iNewUserID; }
	}
	return -1;
}

// Adds a new user to the userlist and returns ID if successfull
// Otherwise -1 is returned
int user_AddNewUser(char *sUsername, int iSocket) {
	user_SetMutex();
	struct UserInfo *oNewUserInfo = malloc(sizeof(struct UserInfo));
	int8_t iUserID;

	// skip if usercount exceeded
	if (user_GetUserCount() > 3) { return -1; }
	// skip if no free user-id found
	iUserID = user_GetFreeUserID();

	oNewUserInfo -> sUsername = malloc(strlen(sUsername) * sizeof(char)+1);
	strcpy(oNewUserInfo -> sUsername, sUsername);
	oNewUserInfo -> iUserID = iUserID;
	oNewUserInfo -> iSocket = iSocket;
	oNewUserInfo -> iPunktestand = 0;
	oNewUserInfo -> bEndphaseAktiv = 0;
	oNewUserInfo -> iQuestionIndex = -1;
	oNewUserInfo -> iTimeoutRemaining = -1;

	// insert new element into list at position 0
	oNewUserInfo -> ptrNext = oUserList;
	oUserList = oNewUserInfo;

	user_FreeMutex();

	return iUserID;
}

struct UserInfo* GetUserByID(uint8_t iUserID) {
	//struct UserInfo *oFirstElementPointer;
	struct UserInfo *oTemp;

	if (oUserList == NULL) { return NULL; }

	oTemp = oUserList;
	while (1) {
		if (oTemp -> iUserID == iUserID) {
			return oTemp;
		}
		// Skip if next entry is null
		if (oTemp -> ptrNext == NULL) { return NULL; }
		oTemp = oTemp -> ptrNext;
	}

	return NULL;
}

// Deletes an existing user from the list, identified by its iID
// Returns 1 if successfull and 0 if an error occured
int user_DeleteUser(uint8_t iUserID) {
	user_SetMutex();
	struct UserInfo *oTemp = oUserList;
	struct UserInfo *oTargetUser = GetUserByID(iUserID);

	// Does any user exist?
	if (oTargetUser == NULL) { return 0; }

	// Is it the first user?
	if (oUserList == oTargetUser) {
		oUserList = oUserList -> ptrNext;
		free(oTargetUser);
		return 1;
	}

	// Loop until we found the user
	while (oTemp -> ptrNext != oTargetUser) {
		oTemp = oTemp -> ptrNext;
	}

	// reconnect the list entries
	oTemp -> ptrNext = oTemp -> ptrNext -> ptrNext;
	free(oTargetUser);

	user_FreeMutex();

	return 1;
}

struct UserInfo* user_GetUserList() {
	return oUserList;
}

// Resets QIndex of all users to -1
void user_ResetAllQuestionIndices() {
	user_SetMutex();
	int i;
	int iCount = user_GetUserCount();
	struct UserInfo *oTemp = oUserList;

	for (i = 0; i < iCount; i++) {
		oTemp -> iQuestionIndex = -1;
		oTemp -> bEndphaseAktiv = 0;
		oTemp -> iTimeoutRemaining = -1;
		oTemp -> iPunktestand = 0;
		oTemp = oTemp -> ptrNext;
	}
	user_FreeMutex();
}

void user_Sort() {
	user_SetMutex();
	int i, j;
	struct UserInfo *oNewUserList = NULL;
	struct UserInfo *oLoopUser;
	struct UserInfo *oTempUser;
	int iScore;
	int iUserCount = user_GetUserCount();

	for (i = 0; i < iUserCount; i++) {
		iScore = 999999;
		oLoopUser = oUserList;
		for (j = 0; j < iUserCount - i; j++) {
			// durchsuche verbleibende user
			if (oLoopUser->iPunktestand < iScore) {
				// neuen low user gefunden
				iScore = oLoopUser->iPunktestand;
				oTempUser = oLoopUser;
			}
			// loop
			oLoopUser = oLoopUser->ptrNext;
		}

		// temp user extrahieren
		oLoopUser = oUserList;

		if (oLoopUser->iUserID == oTempUser->iUserID) {
			// 1. element
			oUserList = oLoopUser->ptrNext;
		} else {
			for (j = 0; j < iUserCount - i - 1; j++) {
				if (oLoopUser->ptrNext->iUserID == oTempUser->iUserID) {
					// user gefunden, extrahieren
					oLoopUser->ptrNext = oLoopUser->ptrNext->ptrNext;
					break;
				}
				// loop
				oLoopUser = oLoopUser->ptrNext;
			}
		}

		// temp user hinzufügen
		oTempUser->ptrNext = oNewUserList;
		oNewUserList = oTempUser;

	}

	oUserList = oNewUserList;

	user_FreeMutex();
}

// Disconnects all users and deletes user data
void user_DisconnectAllUsers() {
	user_SetMutex();
	int i;
	int iCount = user_GetUserCount();
	int *iUserIDs = (int *)malloc(iCount * sizeof(int));
	struct UserInfo *oTemp = oUserList;

	for (i = 0; i < iCount; i++) {
		iUserIDs[i] = oTemp -> iUserID;
		oTemp = oTemp -> ptrNext;
	}
	user_FreeMutex();
	for (i = 0; i < iCount; i++) {
		oTemp = GetUserByID(iUserIDs[i]);
		server_SendErrorMessage(oTemp -> iSocket, "Fehler: Spiel wurde abgebrochen", 1);
		close(oTemp -> iSocket);
		user_DeleteUser(iUserIDs[i]);
	}
}
